﻿/*
COPYRIGHT (C) 2011-2012 Raynald Messié

THIS FILE IS PART OF SCRUMPILOT.

SCRUMPILOT IS FREE SOFTWARE: YOU CAN REDISTRIBUTE IT AND/OR MODIFY IT UNDER THE TERMS OF 
THE GNU LESSER GENERAL PUBLIC LICENSE VERSION v2.1 AS PUBLISHED BY THE FREE SOFTWARE FOUNDATION.

SCRUMPILOT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT WITHOUT ANY WARRANTY; WITHOUT
EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU LESSER
GENERAL PUBLIC LICENSE FOR MORE DETAILS.

YOU SHOULD HAVE RECEIVED A COPY OF THE GNU LESSER GENERAL PUBLIC LICENSE ALONG WITH SCRUMPILOT. 
IF NOT, SEE <HTTP://WWW.GNU.ORG/LICENSES/>.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using ScrumPilot.Helpers;

namespace ScrumPilot.TFSHelpers
{
    public static class WorkItemExtentions
    {
        private static String[] StateLanguageDictonnary(this WorkItem wi)
        {
            // Incredible no but not work, because we should change langage code for importing WIT !
            /* return wi.Project.Store.CultureInfo.TwoLetterISOLanguageName.ToLower() == "fr" ?
                 new[] { "A faire", "En cours", "A testé", "Terminé" } :
                 new[] { "To do", "In Progress", "To Test", "Done" };*/
            return wi.GetWorkItemType() == WorkItemType.Defect
                       ? new[] { "Active", "Resolved" }
                       : new[] { "Active", "Closed" };
        }
        //  readonly static WorkItemType[] NoUserStories = { WorkItemType.UserStory, WorkItemType.Defect };


        #region General Methods

        public static void SetFieldStringValue(this WorkItem wi, String key, String value)
        {
            if (wi.Fields.Contains(key))
                wi.Fields[key].Value = value;
        }

        public static void SetFieldValue<T>(this WorkItem wi, String key, T value) where T : struct
        {
            if (wi.Fields.Contains(key))
                wi.Fields[key].Value = value;
        }

        public static void SetNullableFieldValue<T>(this WorkItem wi, String key, T value)
        {
            if (wi.Fields.Contains(key))
                wi.Fields[key].Value = value;
        }

        public static String GetFieldStringValue(this WorkItem wi, String key)
        {
            if (wi.Fields.Contains(key) && wi.Fields[key].Value != null)
                return wi.Fields[key].Value.ToString();
            return default(String);
        }

        public static T GetFieldValue<T>(this WorkItem wi, String key) where T : struct
        {
            if (wi.Fields.Contains(key) && wi.Fields[key].Value != null)
                return Helper.GetValue<T>(wi.Fields[key].Value.ToString());
            return default(T);
        }

        public static Nullable<T> GetNullableFieldValue<T>(this WorkItem wi, String key) where T : struct
        {
            if (wi.Fields.Contains(key) && wi.Fields[key].Value != null)
                return Helper.GetValue<T>(wi.Fields[key].Value.ToString());
            return null;
        }

        #endregion

        #region Properties

        public static WorkItem GetParentWorkItem(this WorkItem wi, TFSServices tfsw)
        {
            WorkItem wRet = null;
            if (wi.RelatedLinkCount > 0)
            {
                wRet = (from Link lk in wi.Links
                        where lk.ArtifactLinkType != null && lk.ArtifactLinkType.Name == "Related Workitem"
                        where ((RelatedLink)lk).LinkTypeEnd.Name == "Parent"
                        select tfsw.TfsWorkItemStore.GetWorkItem(((RelatedLink)lk).RelatedWorkItemId)).FirstOrDefault();
            }
            return wRet;
        }

        public static List<WorkItem> GetChildWorkItem(this WorkItem wi)
        {
            List<WorkItem> wRet = null;
            if (wi.RelatedLinkCount > 0)
            {
                wRet = (from Link lk in wi.Links
                        where lk.ArtifactLinkType != null && lk.ArtifactLinkType.Name == "Related Workitem" &&
                        ((RelatedLink)lk).LinkTypeEnd.Name == "Child"
                        select wi.Store.GetWorkItem(((RelatedLink)lk).RelatedWorkItemId)).ToList();
            }
            return wRet;
        }

        public static List<int> GetChildWorkItemId(this WorkItem wi)
        {
            List<int> wRet = null;
            if (wi.RelatedLinkCount > 0)
            {
                wRet = (from Link lk in wi.Links
                        where lk.ArtifactLinkType != null && lk.ArtifactLinkType.Name == "Related Workitem" &&
                        ((RelatedLink)lk).LinkTypeEnd.Name == "Child"
                        select ((RelatedLink)lk).RelatedWorkItemId).ToList();
            }
            return wRet;
        }

        public static List<WorkItem> GetTaskChildWorkItem(this WorkItem wi)
        {
            List<WorkItem> wRet = null;
            if (wi.RelatedLinkCount > 0)
            {
                var lstRet = (from Link lk in wi.Links
                              where lk.ArtifactLinkType != null && lk.ArtifactLinkType.Name == "Related Workitem" &&
                              ((RelatedLink)lk).LinkTypeEnd.Name == "Child"
                              select ((RelatedLink)lk).RelatedWorkItemId).ToList();

                if (lstRet.Count > 0)
                {
                    var qry = new TFSQuery(wi.Store);
                    wRet = qry.GetWorkItemList(lstRet, new List<string>(new[] { "Task" }), true);
                }
            }
            return wRet;
        }

        public static List<WorkItem> GetTaskChildWorkItemInSameIteration(this WorkItem wi)
        {
            List<WorkItem> wRet = wi.GetTaskChildWorkItem();
            if (wRet!=null && wRet.Count > 0)
            {
                var currentIteration = wi.IterationId;
                wRet = wRet.Where(p => p.IterationId == currentIteration).ToList();
            }
            return wRet;
        }

        // Scheduling/Sprint
        public static int GetRank(this WorkItem wi)
        {
            return GetFieldValue<int>(wi, "Microsoft.VSTS.Common.StackRank");
        }

        public static void SetRank(this WorkItem wi, int value)
        {
            SetFieldValue(wi, "Microsoft.VSTS.Common.StackRank", value);
        }

        public static Nullable<DateTime> GetStartDate(this WorkItem wi)
        {
            return GetNullableFieldValue<DateTime>(wi, "Microsoft.VSTS.Scheduling.StartDate");
        }

        public static void SetStartDate(this WorkItem wi, Nullable<DateTime> value)
        {
            SetNullableFieldValue(wi, "Microsoft.VSTS.Scheduling.StartDate", value);
        }

        public static Nullable<DateTime> GetFinishDate(this WorkItem wi)
        {
            return GetNullableFieldValue<DateTime>(wi, "Microsoft.VSTS.Scheduling.FinishDate");
        }

        public static void SetFinishDate(this WorkItem wi, Nullable<DateTime> value)
        {
            SetNullableFieldValue(wi, "Microsoft.VSTS.Scheduling.FinishDate", value);
        }

        public static Decimal GetRemainingWork(this WorkItem wi)
        {
            var remainTime = wi.GetWorkItemTime("Microsoft.VSTS.Scheduling.RemainingWork");
            if (remainTime == 0)
            {
                remainTime = wi.GetOriginalEstimate(); // If remaining time is not defined, you should say equal to estimate
            }
            if (wi.IsRealDone())
                remainTime = 0; // if it's done and dev not set remain to 0
            return remainTime;

        }

        public static Decimal GetCompletedWork(this WorkItem wi)
        {
            return wi.GetWorkItemTime("Microsoft.VSTS.Scheduling.CompletedWork");
        }

        public static Decimal GetOriginalEstimate(this WorkItem wi)
        {
            return wi.GetWorkItemTime("Microsoft.VSTS.Scheduling.OriginalEstimate");
        }

        private static Decimal GetWorkItemTime(this WorkItem wi, String field)
        {
            Decimal rRet = 0;
            switch (wi.GetWorkItemType())
            {
                case WorkItemType.Task:
                    rRet = (decimal)GetFieldValue<double>(wi, field);
                    break;
                case WorkItemType.UserStory:
                    var childs = wi.GetTaskChildWorkItem();
                    rRet = childs == null ? 0 : wi.GetTaskChildWorkItem().Sum(p => p.GetCompletedWork());
                    break;
                case WorkItemType.Defect:
                    rRet = Decimal.Round(wi.GetTimeDefect() / 60, 2);
                    break;
            }
            return rRet;
        }

        public static String GetOtherDescription(this WorkItem wi)
        {
            return GetFieldStringValue(wi, "Microsoft.VSTS.TCM.ReproSteps");
        }

        public static void SetOtherDescription(this WorkItem wi, String value)
        {
            SetFieldStringValue(wi, "Microsoft.VSTS.TCM.ReproSteps", value);
        }

        public static int GetPriority(this WorkItem wi)
        {
            return GetFieldValue<int>(wi, "Microsoft.VSTS.Common.Priority");
        }

        public static int GetSeverity(this WorkItem wi)
        {
            return GetFieldValue<int>(wi, "Microsoft.VSTS.Common.Severity");
        }

        public static String GetReason(this WorkItem wi)
        {
            return GetFieldStringValue(wi, "System.Reason");
        }

        public static String GetAssignedTo(this WorkItem wi)
        {
            return GetFieldStringValue(wi, "System.AssignedTo");
        }

        public static void SetAssignedTo(this WorkItem wi, String value)
        {
            SetFieldStringValue(wi, "System.AssignedTo", value);
        }

        public static Nullable<DateTime> GetResolvedDate(this WorkItem wi)
        {
            return GetNullableFieldValue<DateTime>(wi, "Microsoft.VSTS.Common.ResolvedDate");
        }

        public static String GetResolvedBy(this WorkItem wi)
        {
            return GetFieldStringValue(wi, "Microsoft.VSTS.Common.ResolvedBy");
        }

        public static Nullable<DateTime> GetClosedDate(this WorkItem wi)
        {
            return GetNullableFieldValue<DateTime>(wi, "Microsoft.VSTS.Common.ClosedDate");
        }

        public static String GetClosedBy(this WorkItem wi)
        {
            return GetFieldStringValue(wi, "Microsoft.VSTS.Common.ClosedBy");
        }

        public static Nullable<DateTime> GetStateChangeDate(this WorkItem wi)
        {
            return GetNullableFieldValue<DateTime>(wi, " Microsoft.VSTS.Common.StateChangeDate");
        }

        public static String GetStateChangeBy(this WorkItem wi)
        {
            return GetFieldStringValue(wi, "Microsoft.VSTS.Common.StateChangeBy");
        }


        public static String GetActivity(this WorkItem wi)
        {
            return GetFieldStringValue(wi, "Microsoft.VSTS.Common.Activity");
        }

        /* this 4 following field is part of ScrumPilot agile template */
        public static Byte GetLevel(this WorkItem wi)
        {
            return GetFieldValue<Byte>(wi, "ScrumPilot.Score.Level");
        }

        public static void SetLevel(this WorkItem wi, Byte value)
        {
            SetFieldValue(wi, "ScrumPilot.Score.Level", value);
        }

        public static Byte GetPrecedence(this WorkItem wi)
        {
            return GetFieldValue<Byte>(wi, "ScrumPilot.Score.Precedence");
        }

        public static void SetPriority(this WorkItem wi, Byte value)
        {
            SetFieldValue(wi, "ScrumPilot.Score.Precedence", value);
        }

        public static Byte GetDuration(this WorkItem wi)
        {
            return GetFieldValue<Byte>(wi, "ScrumPilot.Score.Duration");
        }

        public static void SetDuration(this WorkItem wi, Byte value)
        {
            SetFieldValue(wi, "ScrumPilot.Score.Duration", value);
        }

        public static Nullable<DateTime> GetScoringDate(this WorkItem wi)
        {
            return GetNullableFieldValue<DateTime>(wi, "ScrumPilot.Score.ScoringDate");
        }

        public static void SetScoringDate(this WorkItem wi, Nullable<DateTime> value)
        {
            SetNullableFieldValue(wi, "ScrumPilot.Score.ScoringDate", value);
        }

        public static Decimal GetTimeDefect(this WorkItem wi)
        {
            Decimal rRet = 0;
            foreach (Revision revision in wi.Revisions)
            {
                // Get value of fields in the work item revision
                foreach (Field field in revision.Fields.Cast<Field>().Where(field => field.Name == "History" && field.Value.ToString().ToLower().StartsWith("&lt;time")))
                {
                    var sVal = field.Value.ToString().ToLower();
                    sVal = sVal.Substring(sVal.IndexOf(':') + 1);
                    sVal = sVal.Substring(0, sVal.Length - 4);
                    rRet = Decimal.Parse(sVal);
                    break;
                }
                if (rRet != 0) break;
            }
            return rRet;
        }

        public static List<Revision> GetRevisionsList(this WorkItem wi)
        {
            var revs = new List<Revision>(wi.Revisions.Count);
            revs.AddRange(wi.Revisions.Cast<Revision>());
            return revs;
        }

        public static Nullable<DateTime> GetDateTaskCompleted(this WorkItem wi)
        {
            Nullable<DateTime> dRet = null;
            var revs = wi.GetRevisionsList();
            var revision = revs.OrderByDescending(o => o.Index).FirstOrDefault(p => p.Fields.Cast<Field>().Any(field => field.ReferenceName == "System.State"
                                                                && field.OriginalValue.ToString().ToLower().StartsWith("active")
                                                                && field.Value.ToString().ToLower().StartsWith("closed")));

            if (revision != null && revision.Fields.Cast<Field>().Any(field => field.ReferenceName == "System.Reason"
                 && field.Value.ToString().ToLower().StartsWith("completed")))
            {
                var dt = revision.Fields.Cast<Field>().FirstOrDefault(field => field.Name == "Closed Date");
                if (dt != null)
                    dRet = dt.Value as DateTime?;
            }
            //    {
            //    var revision = wi.Revisions[i];     
            //      // check if this task is closed
            //    if (revision.Fields.Cast<Field>().Any(field => field.ReferenceName == "System.State"
            //        && field.OriginalValue.ToString().ToLower().StartsWith("active") 
            //        && field.Value.ToString().ToLower().StartsWith("closed")))
            //    {
            //        // Check if task is completed
            //        if(revision.Fields.Cast<Field>().Any(field => field.ReferenceName == "System.Reason"
            //        && field.Value.ToString().ToLower().StartsWith("completed") ))
            //        // Get value of fields in the work item revision
            //        foreach (
            //            Field field in
            //                revision.Fields.Cast<Field>().Where(
            //                    field =>
            //                    field.Name == "Closed Date"))
            //        {
            //            dRet = field.Value as DateTime?;
            //        }
            //    }
            //}
            return dRet;
        }

        public static Nullable<DateTime> GetDateUsCompleted(this WorkItem wi)
        {
            Nullable<DateTime> dRet = null;
            var revs = wi.GetRevisionsList();
            var revision = revs.OrderByDescending(o => o.Index).FirstOrDefault(p => p.Fields.Cast<Field>().Any(field => field.ReferenceName == "System.State"
                                                                && field.OriginalValue.ToString().ToLower().StartsWith("resolved")
                                                                && field.Value.ToString().ToLower().StartsWith("closed")));

            if (revision != null && revision.Fields.Cast<Field>().Any(field => field.ReferenceName == "System.Reason"
                 && field.Value.ToString().ToLower().StartsWith("acceptance tests pass")))
            {
                var dt = revision.Fields.Cast<Field>().FirstOrDefault(field => field.Name == "Closed Date");
                if (dt != null)
                    dRet = dt.Value as DateTime?;
            }
            return dRet;
        }

        public static Nullable<DateTime> GetDateBugCompleted(this WorkItem wi)
        {
            Nullable<DateTime> dRet = null;
            //    if (wi.GetState() == WorkItemState.Done)
            {
                var revs = new List<Revision>(wi.Revisions.Count);
                revs.AddRange(wi.Revisions.Cast<Revision>());
                var revision =
                    revs.OrderByDescending(o => o.Index)
                        .FirstOrDefault(
                            p =>
                            p.Fields.Cast<Field>()
                             .Any(field => field.ReferenceName == "Microsoft.VSTS.Common.ResolvedDate"
                                           && field.OriginalValue == null
                                           && field.Value != null));

                if (revision != null)
                {
                    //var wis = wi.GetState();
                    var dt = revision.Fields["Resolved Date"];
                    if (dt != null)
                        dRet = dt.Value as DateTime?;
                }
            }
            return dRet;
        }



        public static int GetScrumPilotScore(this WorkItem wi)
        {
            var score = 0;
            if (wi.GetScoringDate() != null)
            {
                var l = wi.GetLevel();
                var p = wi.GetPrecedence();
                var d = wi.GetDuration();
                score = l * p * d;
            }
            if (wi.RelatedLinkCount > 0) // there is some sub workitems
            {
                var subWorkItems = wi.GetTaskChildWorkItem(); // Get just scored item => task
                if (subWorkItems != null && subWorkItems.Count > 0)
                    score += subWorkItems.Sum(item => item.GetScrumPilotScore()); // and recurse on childs
            }
            return score;
        }

        public static Boolean GetIsReopen(this WorkItem wi)
        {
            var state = wi.GetState();
            if (state == WorkItemState.InProgress || state == WorkItemState.Todo)
            {
                var reason = wi.GetReason().ToLower();
                return !String.IsNullOrWhiteSpace(reason) && "réactivéreactivate".Contains(reason);
            }
            return false;
        }

        public static WorkItemType GetWorkItemType(this WorkItem wi)
        {
            var tRet = WorkItemType.Unknown;
            try
            {
                if (wi.Type != null)
                {
                    switch (wi.Type.Name)
                    {
                        case "Task":
                        case "Tâche":
                            tRet = WorkItemType.Task;
                            break;
                        case "Bug":
                        case "Bogue": // For french : TFS don't use resource mechanism langage
                            tRet = WorkItemType.Defect;
                            break;
                        case "User Story":
                            tRet = WorkItemType.UserStory;
                            break;
                        default:
                            tRet = WorkItemType.Unknown;
                            break;
                    }
                }
            }
            catch (Exception)
            {
                tRet = WorkItemType.Error;
            }
            return tRet;
        }

        public static WorkItemState GetState(this WorkItem wi)
        {
            WorkItemState stRet;
            WorkItemType type = wi.GetWorkItemType();
            switch (wi.State)
            {
                case "Closed":
                case "Resolved":
                case "Terminé":
                case "Fermé":
                    stRet = WorkItemState.Done;
                    break;
                case "Active":
                case "Actif": // for french version
                    stRet = !String.IsNullOrEmpty(wi.GetAssignedTo()) // && type != WorkItemType.UserStory
                                ? WorkItemState.InProgress
                                : WorkItemState.Todo;
                    if (!String.IsNullOrEmpty(wi.Reason) &&
                        "RéactivéRégressionReactivatedRegressionNot fixedTest Failed".Contains(wi.Reason))
                        stRet = WorkItemState.Reopen;
                    break;
                case "To Test": // From TFS vNext
                    stRet = WorkItemState.ToTest;
                    break;
                default:
                    if (type == WorkItemType.Task)
                    {
                        stRet = WorkItemState.InProgress;
                        break;
                    }
                    stRet = WorkItemState.Todo;
                    break;
            }
            return stRet;
        }

        public static Boolean IsActive(this WorkItem wi)
        {
            return StateActive(wi.GetState());
        }

        private static Boolean StateActive(WorkItemState cState)
        {
            return cState == WorkItemState.Todo || cState == WorkItemState.InProgress || cState == WorkItemState.Reopen;
        }

        public static Boolean IsRealDone(this WorkItem wi)
        {
            String[] res = { "cannot reproduce", "obsolete", "abandoned" };
            String reason = wi.Reason.ToLower();
            return (wi.GetState() == WorkItemState.Done) && res.All(s => !reason.Contains(s));
        }

        public static Boolean IsRemainingCodeReview(this WorkItem wi)
        {
            if (!wi.IsRealDone())
                return false;

            String[] sc = { "code review ok", "no code", "pas de code" };
            String sHist = wi.History.ToLower();
            return sc.All(s => !sHist.Contains(s));
        }

        public static Boolean IsUserStoryReadyToTest(this WorkItem wi)
        {
            if (wi.GetWorkItemType() != WorkItemType.UserStory)
                return false;
            if (!wi.IsActive())
                return false;

            var childWitemsId = wi.GetChildWorkItemId();
            if (childWitemsId == null || !childWitemsId.Any())
                return false;

            var qry = new TFSQuery(wi.Store);
            var childWitems = qry.GetWorkItemList(childWitemsId, new List<string>(new[] { "Task", "Bug" }),
                new List<WorkItemState> { WorkItemState.Todo, WorkItemState.InProgress, WorkItemState.Reopen }, false);

            if (childWitems == null || !childWitems.Any())
                return false;

            var testCount = childWitems.Count(
                p => p.GetWorkItemType() == WorkItemType.Task && p.GetActivity().ToLower().StartsWith("test"));

            return testCount == childWitems.Count();
        }

        public static void SetState(this WorkItem wi, WorkItemState value)
        {
            // Todo : check WI type to set the right value
            // this work ONLY with task & bug
            wi.State = wi.StateLanguageDictonnary()[StateActive(value) ? 0 : 1];
        }

        public static List<TFSAttachment> ReadAttachments(this WorkItem wi, TFSServices tfs)
        {
            var data = new List<TFSAttachment>();
            foreach (Attachment attachment in wi.Attachments)
            {
                string attachmentUri = attachment.Uri.ToString();

                using (var wClient = new WebClient())
                {
                    wClient.Credentials = tfs.TFSNetCredential;
                    data.Add(new TFSAttachment
                                 {
                                     Name = attachment.Name,
                                     Extention = attachment.Extension,
                                     Data = wClient.DownloadData(attachmentUri),
                                     Date = attachment.LastWriteTimeUtc,
                                     Comments = attachment.Comment
                                 });
                }
            }
            return data;
        }
        #endregion

        public static TFSTimeLineObject CastToTimeLineObject(this WorkItem workItem)
        {
            var wallContentObject = new TFSTimeLineObject
                                        {
                                            ID = workItem.Id,
                                            Name = workItem.Title,
                                            Descriptions = workItem.Description,
                                            ProjectName = workItem.Project.Name,
                                            Iteration = workItem.IterationPath,
                                            WorkItemType = workItem.Type.Name,

                                        };
            return wallContentObject;
        }
    }

    public class TFSAttachment
    {
        public String Name { get; set; }
        public String Extention { get; set; }
        public Byte[] Data { get; set; }
        public DateTime Date { get; set; }
        public String Comments { get; set; }
    }

    public enum WorkItemType
    {
        Unknown,
        UserStory,
        Task,
        Defect,
        Sprint,
        Error
    }

    public enum WorkItemState
    {
        Todo,
        InProgress,
        ToTest,
        Done,
        Reopen
    }
}